/*
 * Copyright (C) 2000-2008 Jens Nixdorf and various contributors
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies of the Software, its documentation and marketing & publicity
 * materials, and acknowledgment shall be given in the documentation, materials
 * and software packages that this Software was used.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package org.mopore.jat.core.externalplugins;

import java.io.File;
import java.io.FilenameFilter;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.mopore.jat.Main;
import org.mopore.jat.gui.ProgressDialog;
import org.mopore.jat.gui.Theme;
import org.mopore.jat.gui.WindowController;
import org.mopore.jat.plugin.IHook;
import org.mopore.jat.plugin.IJatPlugin;
import org.mopore.jat.plugin.IJatPluginInternal;
import org.mopore.jat.plugin.IPluginContextInternal;
import org.mopore.jat.plugin.IPluginMetaInformation;
import org.mopore.jat.plugin.PluginEngine;
import org.mopore.jat.plugin.PluginException;
import org.mopore.jat.util.Common;
import org.mopore.jat.util.Version;


public class ExternalPluginManager {
	
	private final Log log = LogFactory.getLog(getClass().getName());
	
	private ExternalPluginManagerDialog externalPluginManagerDialog;

	private final PluginEngine pluginEngine;
	
	private Map<String, IJatPluginInternal> externalPluginMap;

	private PluginHouseKeeping houseKeeping;
	
	private static final int MININUM_WAIT_TIME = 1500;
	
	public ExternalPluginManager( PluginEngine pluginEngine ) {
		this.pluginEngine = pluginEngine;
	}
	
	
	public void showManagerDialog() {
		
		if ( externalPluginManagerDialog == null ) {
			externalPluginManagerDialog = new ExternalPluginManagerDialog( this );
		}
		
		WindowController controller = pluginEngine.getWindowController();
		controller.askToShow( externalPluginManagerDialog, true );
	}

	
	public String[] listExternalPluginsNames(){
		
		Set<String> externalPluginNamesSet = externalPluginMap.keySet();
		int amountOfExternalPlugins = externalPluginNamesSet.size();
		String[] predefinedArray = new String[amountOfExternalPlugins];
		String[] pluginNames = externalPluginNamesSet.toArray(predefinedArray);
		return pluginNames;
	}
	

	public void addExternalPlugin(File selectedFile) {
		ProgressDialog progressDialog = new ProgressDialog( "Adding plugin..." );
		AddExternalPluginRunnable addExternalPluginRunnable = 
			new AddExternalPluginRunnable( selectedFile, this, progressDialog );
		Thread thread = new Thread( addExternalPluginRunnable );
		thread.start();
	}
	
	
	
	/**
	 * Should only be called by plugin engine
	 * @return
	 * @throws PluginException 
	 */
	public Map<String, IJatPluginInternal> collectExternalPlugins() 
		throws PluginException {
		
		File base = Main.getApplicationDir();
		File pluginsFolder = new File( base, "plugins" );
		File[] pluginFolders = pluginsFolder.listFiles(new FilenameFilter(){
			public boolean accept(File dir, String name) {
				// handle dev environment
				if ( name.equals( ".svn" ) ) {
					return false;
				}
				
				return dir.isDirectory();
			}}
		);
		
		externalPluginMap  = new HashMap<String,IJatPluginInternal>();
		houseKeeping = new PluginHouseKeeping( pluginsFolder );
		
		for (File pluginFolder : pluginFolders ) {
			try {
				String folderId = pluginFolder.getName();
				log.info( "Processing: " + folderId );
				boolean deleted = houseKeeping.deleteIfNecessary( folderId );
				if ( deleted ) {
					log.info( "plugin deleted: " + folderId );
				}
				else {
					String pluginName = 
						collectExternalPlugin( pluginFolder, externalPluginMap );
					
					log.info( "plugin collected: " + pluginName );
				}
			} catch (PluginException e) {
				log.error( "could not collect: " + pluginFolder.getName(), e );
			}
		}
		
		return externalPluginMap;
	}


	private String collectExternalPlugin(File pluginFolder,
			Map<String, IJatPluginInternal> pluginMap) throws PluginException {

		List<URL> urlList = new ArrayList<URL>();

		File pluginFile = new File( pluginFolder, "plugin.jar" );
		
		// get plugin.jar
		String folderId = pluginFolder.getName();
		try {
			URL pluginUrl = pluginFile.toURI().toURL();
			urlList.add( pluginUrl );
		} catch (MalformedURLException e) {
			String errorNoUrl = "could not get url to plugin.jar for: " + 
				folderId;
			log.error( errorNoUrl );
			throw new PluginException( errorNoUrl, e );
		}
		
		// get supporting jars of plugin
		File[] pluginLibs = collectExternalPluginLibs( pluginFolder );
		for (File pluginLib : pluginLibs) {
			try {
				URL url = pluginLib.toURI().toURL();
				urlList.add( url );
			} catch (MalformedURLException e) {
				String errorNoUrl = "could not get url for: " + pluginLib.getName();
				log.error( errorNoUrl );
				throw new PluginException( errorNoUrl, e );
			}
		}
		
		int urlArrayLength = urlList.size();
		URL[] urls = urlList.toArray( new URL[ urlArrayLength ] );
		
		ClassLoader classLoader = new URLClassLoader( urls );
		Class<?> c;
		try {
			c = classLoader.loadClass( IHook.hookClassName );
			IHook hook = (IHook) c.newInstance();
			IJatPluginInternal plugin = hook.getPlugin();
			String pluginName = plugin.getPluginName();
			
			pluginMap.put( pluginName, plugin );
			houseKeeping.register( pluginName, folderId );
			return pluginName;
		} catch (Exception e) {
			String noHookMessage = "Can not load hook for: " + folderId;
			log.error( noHookMessage, e );
			throw new PluginException( noHookMessage, e );
		}
		
	}


	private File[] collectExternalPluginLibs(File pluginFolder) {
		
		File pluginLibsFolder = new File( pluginFolder, "libs" );
		File[] supportLibs = pluginLibsFolder.listFiles(new FilenameFilter(){
			public boolean accept(File jarFile, String name) {
				return name.endsWith(".jar");
			}}
		);
		return supportLibs;
	}
	


	
	public Theme getCurrentTheme() {
		Theme currentTheme = pluginEngine.getCurrentTheme();
		return currentTheme;
	}
	
	
	public String getPluginVersionAsText( String pluginName ) {
		String textResult = "error getting version";
		
		try {
			IPluginContextInternal pluginContextInternal = 
				pluginEngine.getPluginContextInternal();
			IJatPlugin plugin = pluginContextInternal.getPlugin(pluginName);
			IPluginMetaInformation metaInformation = plugin.getMetaInformation();
			Version version = metaInformation.getVersion();
			textResult = version.toString();
		}
		catch (Exception e) {
			log.error( textResult, e );
		}
		
		return textResult;
	}

	
	public void removePlugin(String pluginName, ProgressDialog progressDialog ) 
		throws PluginException {
		
		long startTime = System.currentTimeMillis();
		
		progressDialog.setProgress(10);
		
		Class<? extends IJatPlugin> clazz = 
			externalPluginMap.get( pluginName ).getClass();
		String packageName = clazz.getPackage().getName();
		clazz = null;
		
		String currentFocusOn = 
			pluginEngine.getPluginContextInternal().getPluginNameWithFocus();
		boolean hasFocus = pluginName.equals( currentFocusOn );
		if ( hasFocus ) {
			log.info( "taking away focus" );
			pluginEngine.changeFocus( null );
		}
		
		pluginEngine.shutDownPlugin( pluginName );
		pluginEngine.informPluginsChange();
		
		externalPluginMap.remove( pluginName );
		progressDialog.setProgress(40);
		System.gc();
		try {
			Thread.sleep( 1000 );
		} catch (InterruptedException e) {
			throw new PluginException( e );
		}
		progressDialog.setProgress(65);
		
		houseKeeping.remove( pluginName );
		
		progressDialog.setProgress(80);
		
		File directory = Main.META_DIR;
		File pluginMetaDir = new File( directory, packageName );
		if ( pluginMetaDir.exists() ) {
			Common.deleteDir( pluginMetaDir );
		}
		
		progressDialog.setProgress(90);
		
		List<IJatPluginInternal> list = 
			pluginEngine.getPluginContextInternal().getFocusablePlugins();
		if ( list.size() == 1 ) {
			IJatPluginInternal focusCandidate = list.get( 0 );
			String newFocusPluginName = focusCandidate.getPluginName();
			pluginEngine.changeFocus( newFocusPluginName );
			pluginEngine.informPluginsChange();
		}
		
		externalPluginManagerDialog.removePluginEntry(pluginName);
		
		long endTime = System.currentTimeMillis();
		long duration = endTime - startTime;
		if ( duration < MININUM_WAIT_TIME ) {
			progressDialog.setProgress(100);
			long waitTime = ( MININUM_WAIT_TIME - duration );
			try {
				Thread.sleep( waitTime );
			} catch (InterruptedException e) {
				throw new PluginException( e );
			}
		}
	}


	public void addEntryToPluginList(String pluginName) {
		externalPluginManagerDialog.addPluginEntry( pluginName );
	}


	public void postLoadPlugin( File pluginFolder) throws PluginException {
		String pluginName = 
			collectExternalPlugin( pluginFolder, externalPluginMap );
		
		IJatPluginInternal plugin = externalPluginMap.get( pluginName );
		pluginEngine.postLoadPlugin( plugin );
	}


	public boolean checkAlreadyInstalled(String pluginName) {
		IPluginContextInternal context = 
			pluginEngine.getPluginContextInternal();
		IJatPlugin plugin = context.getPlugin( pluginName );
		
		boolean alreadyInstalled = ( plugin != null );
		return alreadyInstalled;
	}
	
}
